#include "interrupt.h"
#include "stdint.h"
#include "global.h"
#include "io.h"
#include "print.h"

#define PIC_M_CTRL 0x20         //主片的控制端口是 0x20
#define PIC_M_DATA 0x21         //主片的数据端口是 0x21
#define PIC_S_CTRL 0xa0         //从片的控制端口是 0xa0
#define PIC_S_DATA 0xa1         //从片的数据端口是 0xa1

#define IDT_DESC_CNT 0x81       //目前总共支持的中断数

#define EFLAGS_IF 0x00000200    //eflags寄存器内IF为1
#define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; popl %0" \
                                           : "=g"(EFLAG_VAR));

extern uint32_t syscall_handler(void);
/* 中断门描述符结构体 */
struct gate_desc{
    uint16_t func_offset_low_word;
    uint16_t selector;
    uint8_t dcount;             //此项为双字计数字段,是门描述符中的第4字节,固定值，不用考虑
    uint8_t attribute;
    uint16_t func_offset_high_word;
};

//静态函数声明
static void make_idt_desc(struct gate_desc *p_gdesc, uint8_t attr, intr_handler function);
static struct gate_desc idt[IDT_DESC_CNT];  //中断描述符表,数组

char *intr_name[IDT_DESC_CNT];              //保存异常的名字
intr_handler idt_table[IDT_DESC_CNT];       //定义中断处理程序数组在 kernel.S 中定义的 intrXXentry,只是中断处理程序的入口，最终调用的是 ide_table 中的处理程序
extern intr_handler intr_entry_table[IDT_DESC_CNT]; // 声明引用定义在kernel.S中的中断处理函数入口数组

/* 初始化可编程中断控制器8259A */
static void pic_init(void){

    /*初始化主片*/
    outb(PIC_M_CTRL, 0x11);
    outb(PIC_M_DATA, 0x20);
    outb(PIC_M_DATA, 0x04);
    outb(PIC_M_DATA, 0x01);

    /*初始化从片*/
    outb(PIC_S_CTRL, 0x11);
    outb(PIC_S_DATA, 0x28);
    outb(PIC_S_DATA, 0x02);
    outb(PIC_S_DATA, 0x01);

    /*打开主片上IR0,IR1,IR2,也就是目前只接受时钟,键盘,级联从片IRQ14产生的中断*/
    outb(PIC_M_DATA, 0xf8); // 11111000
    outb(PIC_S_DATA, 0xbf); // 10111111

    put_str("   pic_init done\n");
}

/* 创建中断门描述符,在idt_desc_init中循环调用 */
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function){
    p_gdesc->func_offset_low_word = (uint32_t)function & 0x0000FFFF;
    p_gdesc->selector = SELECTOR_K_CODE;
    p_gdesc->dcount = 0;
    p_gdesc->attribute = attr;
    p_gdesc->func_offset_high_word = ((uint32_t)function & 0xFFFF0000) >> 16;
}

/* 初始化中断描述符表 */
static void idt_desc_init(void){
    int i, lastindex = IDT_DESC_CNT - 1;
    for (i = 0; i < IDT_DESC_CNT; i++)
    {
        make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, intr_entry_table[i]);
    }
    // 单独处理系统调用,对应的中断门dpl为3,中断处理程序为单独的syscall_handler
    make_idt_desc(&idt[lastindex], IDT_DESC_ATTR_DPL3, syscall_handler);
    put_str("   idt_desc_init done\n");
}

/* 通用的中断处理函数，一般用在异常出现时的处理 */
static void general_intr_handler(uint8_t vec_nr){//IRQ7 和 IRQ15 会产生伪中断（spurious interrupt）,无需处理,0x2f 是从片 8259A 上的最后一个 IRQ 引脚，保留项
    if (vec_nr == 0x27 || vec_nr == 0x2f){
        return;
    }

    set_cursor(0);
    int cursor_pos = 0;
    while (cursor_pos < 80*4) {
        put_char(' ');  // 清空四行内容
        cursor_pos++;
    }

    set_cursor(0);
    put_str("!!!!!  exception message begin !!!!!");
    set_cursor(88);
    put_str(intr_name[vec_nr]);
    put_str("\n");
    if (vec_nr == 14)
    {
        // page fault
        int page_fault_addr = 0;
        asm("movl %%cr2, %0"
            : "=r"(page_fault_addr)); // cr2是存放page_fault的地址
        put_str("page fault addr is ");
        put_int(page_fault_addr);
        put_str("\n");
    }
    put_str("!!!!!  exception message end !!!!!");
    // 能进入中断处理程序,说明此时已经关中断
    while(1); // 死循环不会再被中断

    // put_str("int vector : 0x");
    // put_int(vec_nr);
    // put_char('\n');
}

/* 完成一般中断处理函数注册及异常名称注册 */
static void exception_init(void){
    int i;
    for (i = 0; i < IDT_DESC_CNT; i++){
    /* idt_table 数组中的函数是在进入中断后根据中断向量号调用的
        见 kernel/kernel.S 的 call [idt_table + %1*4] */
        idt_table[i] = general_intr_handler;// 默认为 general_intr_handler
        // 以后会由 register_handler 来注册具体处理函数
        intr_name[i] = "unknown";// 先统一赋值为 unknown
    }
    intr_name[0] = "#DE Divide Error";
    intr_name[1] = "#DB Debug Exception";
    intr_name[2] = "NMI Interrupt";
    intr_name[3] = "#BP Breakpoint Exception";
    intr_name[4] = "#OF Overflow Exception";
    intr_name[5] = "#BR BOUND Range Exceeded Exception";
    intr_name[6] = "#UD Invalid Opcode Exception";
    intr_name[7] = "#NM Device Not Available Exception";
    intr_name[8] = "#DF Double Fault Exception";
    intr_name[9] = "Coprocessor Segment Overrun";
    intr_name[10] = "#TS Invalid TSS Exception";
    intr_name[11] = "#NP Segment Not Present";
    intr_name[12] = "#SS Stack Fault Exception";
    intr_name[13] = "#GP General Protection Exception";
    intr_name[14] = "#PF Page-Fault Exception";
    // intr_name[15] 第 15 项是 intel 保留项,未使用
    intr_name[16] = "#MF x87 FPU Floating-Point Error";
    intr_name[17] = "#AC Alignment Check Exception";
    intr_name[18] = "#MC Machine-Check Exception";
    intr_name[19] = "#XF SIMD Floating-Point Exception";
}



/* 开中断并返回开中断前的状态 */
enum intr_status intr_enable(){
    enum intr_status old_status;
    if(INTR_ON == intr_get_status()){
        old_status = INTR_ON;
        return old_status;
    }else{
        old_status = INTR_OFF;
        asm volatile("sti");//开中断,sti指令将IF位置1
        return old_status;
    }
}

/* 关中断并返回关中断前的状态 */
enum intr_status intr_disable(){
    enum intr_status old_status;
    if(INTR_ON == intr_get_status()){
        old_status = INTR_ON;
        asm volatile("cli": : : "memory");//关中断,cli指令将IF位置0
        return old_status;
    }else{
        old_status = INTR_OFF;
        return old_status;
    }
}

/* 将中断设置为status,并返回设置前状态 */
enum intr_status intr_set_status(enum intr_status status){
    return (status & INTR_ON) ? intr_enable() : intr_disable();
}

/* 获取当前中断状态 */
enum intr_status intr_get_status(){
    uint32_t eflags = 0;
    GET_EFLAGS(eflags);
    return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF;
}

/* 在中断处理程序数组第vector_no个元素中注册安装中断处理程序function */
void register_handler(uint8_t vector_no, intr_handler function){
    /** 在idt_table数组中的函数是在进入中断后根据中断向量号调用的
     * 见kernel.S的call [idt_table + %1*4] */
    idt_table[vector_no] = function;
}



/*完成有关中断的所有初始化工作*/
void idt_init(void){
    put_str("idt_init_start\n");
    idt_desc_init();                //初始化中断描述符表
    exception_init();
    pic_init(); //初始化 8259A

    /* 加载idt */
    //uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)((uint32_t)idt << 16)));//书中写法
    uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)((uint32_t)idt)) << 16);//与书上不同的写法
    asm volatile("lidt %0" : : "m" (idt_operand));
    put_str("idt_init done\n");
}